package graphdisplay;

import java.util.Random;

public class Graph {

    private double rating;
    public boolean[] bitstring;
    private int len;

    public Graph(boolean[] bits, int len) {
        bitstring = bits;
        this.len = len;
        this.rating = calcRating();
    }

    public Graph(String s, int len) {
        int length = s.length();
        bitstring = new boolean[length];
        for (int i = 0; i < length; i++) {
            bitstring[i] = (s.charAt(i) == '1') ? true : false;
        }
        this.len = len;
        this.rating = calcRating();
    }
    public Graph(Random r, int len, boolean even) {
        this.len = len;
        bitstring = new boolean[len*(len-1)/2];

        if (even) {
            fillEven(r);
        } else {
            fill(r);
        }
        this.rating = calcRating();
    }

    public int size() {
        return len;
    }

    /**
     * Fills the bit string with an even number of ones and zeros
     * @param r The source of the randomness
     */
    private void fillEven(Random r) {
        int ran, i = 0;
        int stringLen = bitstring.length;
        int a = stringLen/2;
        int b = stringLen - a;
        while (a+b > 0) {
            ran = r.nextInt(a+b);
            if (ran < a) {
                a--;
                bitstring[i] = true;
            } else {
                b--;
                bitstring[i] = false;
            }
            i++;
        }
    }

    /**
     * Fills the bit string with random bits
     * @param r The source of the random bits
     */
    private void fill(Random r) {
        int stringLen = bitstring.length;
        for (int i = 0; i < stringLen; i++) {
            bitstring[i] = r.nextBoolean();
        }
    }


    /**
     * Returns the state of the connection between vertex x and y.
     * @param x the first vertex
     * @param y the second vertex
     * @return whether they are connected
     */
    public boolean get(int x, int y) {
        // you can't go and see if a node connects to itself
        if ( x == y ) {
            throw new IllegalArgumentException("Error in Graph.get: " +
                    "Cannot compare a node with itself");
        }
        // swap if x if greater than y
        if (x > y) {
            int temp = y;
            y = x;
            x = temp;
        }
        return bitstring[y + x*len - (x+1)*(x+2)/2]; // magic
    }

    /**
     * Sets the color of a specific connection
     * @param x The first vertex
     * @param y The second vertex
     * @param bit the color
     */
    public void set(int x, int y, boolean bit) {

        if ( x == y ) {
            throw new IllegalArgumentException("Error in Graph.get: " +
                    "Cannot compare a node with itself");
        }
        // swap if x if greater than y
        if (x > y) {
            int temp = y;
            y = x;
            x = temp;
        }
        bitstring[y + x*len - (x+1)*(x+2)/2] = bit;
    }

    /**
     * Creates a string of '1' and '0' characters representing the contents
     * of the bit string
     * @return the bit string
     */
    @Override
    public String toString() {
        StringBuilder out = new StringBuilder();
        for (boolean b : bitstring) {
            out.append((b)?'1':'0');
        }
        return out.toString();
    }

    private double calcRating() {
        return countCliques(); 
    }

    public double getRank() {
        return rating;
    }

    public static Graph mutate(Graph g, int index) {
        int size = g.bitstring.length;
        boolean[] bits = new boolean[size];
        System.arraycopy(g.bitstring, 0, bits, 0, size);
        bits[index] = ! bits[index];
        return new Graph(bits, g.len);
    }

    @Override
    public Graph clone() {
        int size = this.bitstring.length;
        boolean[] bits = new boolean[size];
        System.arraycopy(this.bitstring, 0, bits, 0, size);
        return new Graph(bits, this.len);
    }

    public void mutate(int index) {
        bitstring[index] = ! bitstring[index];
    }


    public int compareTo(Graph other) {
        double otherRank = other.getRank(), thisRank = this.getRank();
        if (otherRank > thisRank) {
            return 1;
        } else if (otherRank < thisRank) {
            return -1;
        }
        return 0;
    }

    public int countCliques() {
        boolean[][] graph = asArray();
        int size = len, count = 0;
        boolean color;
        for (int a = 0; a < size; a++) {
            for (int b = a + 1; b < size; b++) {

                color = graph[a][b];

                for (int c = b+1; c<size; c++) {
                    if ( graph[a][c] != color
                            || graph[b][c] != color ) {
                        continue;
                    }

                    for (int d = c+1; d<size; d++) {

                        if ( graph[a][d] != color
                                || graph[b][d] != color
                                || graph[c][d] != color ) {
                            continue;
                        }

                        for (int e = d+1; e<size; e++) {

                            if ( graph[a][e] == color
                                    && graph[b][e] == color
                                    && graph[c][e] == color
                                    && graph[d][e] == color ) {
                                count++;
                            }
                        }
                    }
                }
            }
        }
        return count;
    }

    public String toMatrix() {
        StringBuilder s = new StringBuilder();

        for ( int i = 0; i < len; i++ ) {
            s.append("[ ");
            for ( int j = 0; j < len; j++ ) {
                if ( i == j ) s.append("~ ");
                else if ( get(i, j) ) s.append("1 ");
                else s.append("0 ");
            }
            s.append("]\n");
        }

        return s.toString();
    }

    public int countZeros() {
        int x = 0;
        for (boolean b : bitstring) if (!b) x++;
        return x;
    }

    public int countOnes() {
        int x = 0;
        for (boolean b : bitstring) if (b) x++;
        return x;
    }

    public void printInfo() {
        System.out.println("Rank: " + getRank());
        System.out.println("5-Cliques: " + countCliques() );
        System.out.println("Blue connections: " + countZeros() );
        System.out.println("Red connections: " + countOnes() );
        System.out.println("Bitstring:");
        System.out.println(toString());
    }

    public boolean[][] asArray() {
        boolean[][] out = new boolean[len][len];
        int index = 0; // Position in bitstring
        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len; j++) {
                out[i][j] = bitstring[index];
                out[j][i] = bitstring[index];
                index++;
            }
        }
        return out;
    }

}
